package com.howie.gulimall.product.web;

import com.howie.gulimall.product.entity.CategoryEntity;
import com.howie.gulimall.product.service.CategoryService;
import com.howie.gulimall.product.vo.gulimall_vo.Catelog2Vo;
import org.redisson.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @description: 处理首页请求
 * @author: laizhenghua
 * @date: 2021/1/24 9:37
 */
@Controller
public class IndexController {
    @Autowired
    private CategoryService categoryService;
    @Autowired
    private RedissonClient redisson; // 引入redissonClient对象
    @Autowired
    private StringRedisTemplate redisTemplate;

    @GetMapping(path = {"/","/index.html"})
    public String indexPage(Model model){
        // TODO: 1、查出所有的1级分类
        List<CategoryEntity> categoryEntities = categoryService.getAllCategoryWithLevel1();
        /*
        默认前缀：public static final String DEFAULT_PREFIX = "classpath:/templates/";
	    默认后缀：public static final String DEFAULT_SUFFIX = ".html";
	    return "index"; ==> 视图解析器拼接为：classpath:/templates/index.html
        */
        model.addAttribute("categorys",categoryEntities); // 存入request域里
        return "index";
    }
    @ResponseBody
    @GetMapping("/index/catalog.json")
    public Map<String, List<Catelog2Vo>> getCatalogJson(){
        // System.out.println("查询json...");
        Map<String, List<Catelog2Vo>> map = categoryService.getCatalogJson();
        return map;
    }
    /*
     * 性能测试请求
     */
    @GetMapping(path = "/hello")
    @ResponseBody
    public String performanceTest() {
        // 1、获取一般锁
        RLock lock = redisson.getLock("my-lock"); // 只要名字相同就是同一把锁
        // 2、加锁
        lock.lock(); // 阻塞式等待
        /*lock.lock(10, TimeUnit.SECONDS); // 10s后自动释放锁，自动释放锁时间一定要大于业务执行时间。
        问题：这种指定时间的锁，在锁时间到了以后，是不会自动续期的。
        原因：
            1、如果我们传递了所得超时时间，就发送给redis执行脚本，进行占锁，默认超时就是我们指定的时间。
            2、如果我们未指定锁的超时时间，就使用30*1000(LockWatchdogTimeout看门狗的默认时间)
        看门狗机制：
            只要占锁成功，就会启动一个定时任务(重新给锁设置过期时间，新的过期时间就是看门狗的默认时间)，每隔10秒都会自动的再次续期，续成30秒
            internalLockLeaseTime(看门狗时间)/3 = 10
        推荐使用：lock.lock(10, TimeUnit.SECONDS); // 省掉了整个自动续期时间
         */
        /*
        关于Redisson的锁：
            1、锁的自动续期，如果业务超长，运行期间自动给多续上新的30s(默认30s)。不用担心业务时间长，锁自动过期被删除掉
            2、加锁的业务只要运行完成，就不会给当前锁续期，即使不手动解锁，锁默认在30s以后自动删除
         */
        try{
            // 业务代码
            System.out.println("加锁成功，执行业务代码..." + Thread.currentThread().getId());
            Thread.sleep(30000); // 30s
        } catch(Exception e){
            e.printStackTrace();
        } finally{
            // 3、解锁。假设解锁代码没有运行，Redisson会不会出现死锁？？答案是不会。原因：自动续期+过期自动删除
            System.out.println("释放锁..." + Thread.currentThread().getId());
            lock.unlock();
        }
        return "hello";
    }

    /**
     * 测试分布式锁-读写锁(改数据加写锁，读数据加读锁)
     * 文档地址：https://github.com/redisson/redisson/wiki/8.-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%92%8C%E5%90%8C%E6%AD%A5%E5%99%A8
     * 作用：可以保证读到最新数据。修改期间，写锁是一个排他锁(互斥锁，独享锁)。读锁是一个共享锁。写锁不释放，读锁就必须等待。
     * 读 + 读：相当于无锁状态。并发读，只会在redis中记录好，所有当前的读锁。他们都会同时加锁成功。
     * 写 + 读：等待写锁释放
     * 写 + 写：阻塞方式
     * 读 + 写：有读锁。写也需要等待
     * 总结：只要有写的操作，都必须等待
     * @return String
     */
    @GetMapping("/write")
    @ResponseBody
    public String readWriteLockWithWrite(){
        String uuid = "";
        RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
        RLock lock = readWriteLock.writeLock(); // 加写锁
        lock.lock();
        try {
            uuid = UUID.randomUUID().toString();
            Thread.sleep(30000);
            redisTemplate.opsForValue().set("uuid",uuid);
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        return uuid;
    }
    @GetMapping("/read")
    @ResponseBody
    public String readWriteLockWithRead(){
        String uuid = "";
        RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
        RLock lock = readWriteLock.readLock(); // 加读锁
        lock.lock();
        try {
            uuid = redisTemplate.opsForValue().get("uuid");
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        return uuid;
    }
    /**
     * redisson分布式锁，信号量(Semaphore)测试。(模拟场景：车库停车，有三个车库)
     * 信号量的使用场景：分布式限流
     * @return: java.lang.String
     */
    @GetMapping("/park")
    @ResponseBody
    public String park() throws InterruptedException {
        RSemaphore park = redisson.getSemaphore("park");
        // 1、获取一个信号(一个值)，相当于拿一个车位
        park.acquire(); // 阻塞式获取车位，没有车位时，一直等待
        // boolean isPark = park.tryAcquire(); 非阻塞式，如果没有车位就返回false
        /*
        if(isPark){
            // 执行业务
        }else{
            // 限流提示
        }
         */
        return "ok";
    }
    @GetMapping("/go")
    @ResponseBody
    public String go(){
        RSemaphore park = redisson.getSemaphore("park");
        // 1、释放一个信号，相当于释放一个车位
        park.release();
        return "";
    }

    /**
     * redisson闭锁。(模拟场景：学校放假关大门，假设有5个班，5个班走完才可以关大门)
     */
    @GetMapping("/lockDoor")
    @ResponseBody
    public String lockDoor() throws InterruptedException {
        RCountDownLatch door = redisson.getCountDownLatch("door");
        door.trySetCount(5);
        door.await(); // 等待闭锁都完成
        return "放假了关大门！";
    }
    /**
     * 模拟5个班放假
     */
    @GetMapping("/recess/{id}")
    @ResponseBody
    public String recess(@PathVariable Long id){
        RCountDownLatch door = redisson.getCountDownLatch("door");
        door.countDown(); // 计数减一(相当于放假)
        return id + "班放假了...";
    }
}
